// 
function defineReactive(obj, key, val) {
  // 一开始只是定义了一个响应式，并没有赋值
  // 如果val是个对象，就需要递归
  observe(val)

  // 创建dep实例，
  let dep = new Dep()
  // 这个key是和这个dep对应的
  Object.defineProperty(obj, key, {
    get() {
      console.log(key, val, 'get');
      // 在这进行依赖收集工作--为什么要静态的target
      // Dep.target--当前的watcher实例
      Dep.target && dep.addDep(Dep.target)
      return val
    },
    set(newVal) {
      if (val != newVal) {
        val = newVal
        // 新赋值的是对象，需要递归处理
        observe(newVal)
        console.log('set', key, val);
        // 当有值改变的时候就更新依赖
        dep.notify()//界面的所有watcher都会进行更新
      }
    }
  })
}

// 对象调用observe方法,实现key的代理拦截
function observe(obj) {
  if (typeof obj != 'object' || obj === null) {
    return obj
  }

  new Observer(obj)
}
// 能够将传入得对象所有的key代理到指定对象上
function proxy(vm) {
  Object.keys(vm.$data).forEach(key => {
    // 拦截实例上的data属性
    Object.defineProperty(vm, key, {

      get() {
        return vm.$data[key]
      },
      set(v) {
        vm.$data[key] = v
      }
    })
  })
}
class CVue {
  constructor(options) {
    // 保存选项
    this.$options = options
    // 频繁访问data
    this.$data = options.data

    // 1、对data进行响应式处理
    observe(options.data)
    // 2、代理data
    proxy(this)
    // 3、编译
    new Compile(options.el, this) // this，当前组件实例
  }
}
// 源码是通过 Observer 实现的，劫持监听所有属性
class Observer {
  constructor(obj) {
    // 判断传入的类型
    if (Array.isArray(obj)) {

    } else {
      this.walk(obj)
    }
  }

  walk(obj) {
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key, obj[key])
    })
  }
}
// 1、模板编译，
// 判断是文本(表达式)还是元素（有没有c-开头的属性），
// 递归模板节点
// 2、建立依赖(发现一个动态的值得时候都应该生成一个更新函数)

class Compile {
  constructor(el, vm) {
    this.$vm = vm
    this.$el = document.querySelector(el)

    if (this.$el) {
      // 如果节点存在就对节点进行编译
      this.compile(this.$el)
    }
  }

  // 遍历node，判断节点类型，并做相应的处理
  compile(node) {
    let childNodes = node.childNodes
    // 类数组可以直接foreach
    Array.from(childNodes).forEach(n => {
      // 判断类型
      if (this.isElement(n)) {
        console.log(n.nodeName)
        this.compileElement(n)
        // 递归，如果子元素还有节点
        if (n.childNodes.length > 0) {
          this.compile(n)
        }
      } else if (this.isInter(n)) {
        // 动态差值表达式
        // console.log('编译文本',n.textContent,'============');
        this.compileText(n)
      }


    })
  }
  // 判断是否是元素
  isElement(n) {
    return n.nodeType === 1
  }
  // 判断是否有差值表达式
  isInter(n) {
    // RegExp.$1静态分组
    return n.nodeType === 3 && /\{\{(.*)\}\}/.test(n.textContent)
  }
  // 编译差值文本{{xxx}}
  compileText(e) {
    // 先获取表达式
    // RegExp.$1
    // n.textContent = this.$vm[RegExp.$1]
    this.update(e, RegExp.$1, 'text')
  }

  // 编译元素，
  compileElement(n) {
    let attrs = n.attributes
    Array.from(attrs).forEach(attr => {
      // attr是对象，name，value
      let attrName = attr.name
      let exp = attr.value
      // 判断是不是指令
      if (this.isDir(attrName)) {
        let dir = attrName.substring(2)
        this[dir] && this[dir](n, exp)
      }
    })

  }
  // 判断是否是指令
  isDir(attrName) {
    return attrName.startsWith('k-')
  }

  // 通用函数，减少代码的冗余
  // 
  update(node, exp, dir) {
    // 节点，表达式，指令
    // 1、init 遇到任何一个动态值得时候，一定是执行update函数，把节点和表达式传进去，同时要多传一个当前这个指令的名称，然后执行这个更新函数
    let fn = this[dir + 'Updater']
    fn && fn(node, this.$vm[exp])

    // 2. update Watcher将来会被dep去调用
    new Watcher(this.$vm, exp, val => {
      // 接受一个最新的值，去更新这个节点
      // update被执行一次就会形成一个闭包，就会保存当前节点和表达式
      fn && fn(node, val)
    })
  }

  // k-text
  text(e, exp) {
    // 只是调用update的方法，并不是真的执行
    this.update(e, exp, 'text')
    // e.textContent = this.$vm[exp]
  }

  // 真正的方法 操作的节点，表达式-即值
  textUpdater(e, val) {
    // val this.$vm[exp]
    e.textContent = val
  }

  // k-html
  html(e, exp) {
    // e.innerHTML = this.$vm[exp]
    this.update(e, exp, 'html')
  }
  // 任何一个指令都要有一个相对应的updater
  htmlUpdater(e, val) {
    e.innerHTML = val
  }
}

// 新的类watch，观察者，发布订阅模式 
// 绑定和那个key有关,与之相对应的更新函数
// dep数组,保存多个watcher


// 负责dom更新 ---什么时候创建(编译节点的时候)，怎么更新？
class Watcher {
  constructor(vm, key, updater) {
    this.vm = vm
    this.key = key
    // updater是一个回调，形成闭包，更新节点最新的值
    this.updater = updater

    // 触发依赖收集，get    也可以用一个全局的target，主要目的就是实现依赖收集
    Dep.target = this
    this.vm[this.key]
    Dep.target = null
  }
  // 将来这个会被Dep调用
  update() {
    // 告诉这个实例，最新的值是this.vm[this.key]
    this.updater.call(this.vm, this.vm[this.key])
  }
}


// 保存所有watcher实例的依赖类
// 什么时候创建dep？（在遍历响应式的时候）  建立依赖？（在get的时候建立）  在set的时候更新视图
class Dep {
  constructor() {
    this.deps = []
  }

  // 这里的dep就是每一个watcher实例
  addDep(dep){
    // 创建依赖关系的时调用
    this.deps.push(dep)
  }
  notify(){
    this.deps.forEach(dep=>dep.update())
  }
}